/* Hybrid System Abstractor
 *
 * Copyright (c) 2013, Fortiss GmbH - An-Institut TU Muenchen
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package org.fortiss.util;

import javax.xml.bind.annotation.*;



@XmlRootElement
public class IntervalMatrix
{
    @XmlAttribute
    private int row;
    @XmlAttribute
    private int column;
    @XmlElement
    private ListWrapper<ListWrapper<Interval>> matrix;
        
    public IntervalMatrix()     {}
    
    public IntervalMatrix(double m[][])
    {
        row = m.length;
        column = m.length>0 ? m[0].length : 0;
        matrix = new ListWrapper<ListWrapper<Interval>>(m.length);
        for (int i=0; i<m.length; i++){
            matrix.add(new ListWrapper<Interval>(m[i].length));
            for (int j=0; j<m[i].length; j++)
                matrix.get(i).add(new Interval(m[i][j], m[i][j]));
        }
    }
    
    private IntervalMatrix(int r, int c)
    {
        this.row = r;
        this.column = c;
        matrix = new ListWrapper<ListWrapper<Interval>>(r);
        for (int i=0; i<r; i++){
            matrix.add(new ListWrapper<Interval>(c));
            for (int j=0; j<c; j++)
                matrix.get(i).add(null);
        }
    }
    
    public static IntervalMatrix eye(int n)
    {
        double m[][] = new double[n][];
        for (int i=0; i<n; i++) {
            m[i] = new double[n];
            m[i][i] = 1;
        }
        return new IntervalMatrix(m);
    }
    
    public static IntervalMatrix zero(int row, int column)
    {
        double m[][] = new double[row][];
        for (int i=0; i<row; i++) {
            m[i] = new double[column];
        }
        return new IntervalMatrix(m);
    }
    
    public int getRow()    { return this.row; }
    public int getColumn() { return this.column; }
    
    public Interval getElement(int i, int j)
    {
        return matrix.get(i).get(j);
    }
    private void setElement(int r, int c, Interval i)
    {
        matrix.get(r).set(c, i);
    }

    public void println()
    {
        System.out.format("[ ");
        for (int i=0; i<this.getRow(); i++){
            for (int j=0; j<this.getColumn(); j++)
                this.getElement(i, j).print();
            System.out.format("; ");
        }
        System.out.format("]\n");
    }
    
    public IntervalMatrix plus(IntervalMatrix m)
    {
        IntervalMatrix ret = new IntervalMatrix(this.getRow(), this.getColumn());
        for (int i=0; i<this.getRow(); i++)
            for (int j=0; j<this.getColumn(); j++)
                ret.setElement(i, j, this.getElement(i, j).plus(m.getElement(i, j)));
        return ret;
    }

    public IntervalMatrix times(double s)
    {
        IntervalMatrix ret = new IntervalMatrix(this.getRow(), this.getColumn());
        for (int i=0; i<this.getRow(); i++)
            for (int j=0; j<this.getColumn(); j++)
                ret.setElement(i, j, this.getElement(i, j).times(s));
        return ret;
    }

    public IntervalMatrix times(Interval iv)
    {
        IntervalMatrix ret = new IntervalMatrix(this.getRow(), this.getColumn());
        for (int i=0; i<this.getRow(); i++)
            for (int j=0; j<this.getColumn(); j++)
                ret.setElement(i, j, this.getElement(i, j).times(iv));
        return ret;
    }

    public IntervalMatrix times(IntervalMatrix m)
    {
        IntervalMatrix ret = new IntervalMatrix(this.getRow(), m.getColumn());
        for (int i=0; i<ret.getRow(); i++)
            for (int j=0; j<ret.getColumn(); j++){
                Interval iv = new Interval(0, 0);
                for (int k=0; k<this.getColumn(); k++)
                    iv = iv.plus(this.getElement(i, k).times(m.getElement(k, j)));
                ret.setElement(i, j, iv);
            }
  
        return ret;
    }

    public IntervalMatrix expm()
    {
        return hornerTaylor();
    }
    
    private IntervalMatrix hornerTaylor()
    {
        int K = 20;
        int n = this.getColumn();
        IntervalMatrix result = IntervalMatrix.eye(n);
        
        for (int i = K; i>0; i--) {
            result = IntervalMatrix.eye(n).plus(result.times(this).times(1.0/i));
        }
        
        double alpha = 0;
        for (int i=0; i<n; i++)
            for (int j=0; j<n; j++) {
                alpha = Math.max(alpha, Math.abs(this.getElement(i, j).getLower()));
                alpha = Math.max(alpha, Math.abs(this.getElement(i, j).getUpper()));
            }
        double rho = 1;
        for (int i=0; i<=K+1; i++) rho *= alpha;
        for (int i=1; i<=K+1; i++) rho /= i;
        rho /= 1-(alpha/(K+2));
        for (int i=0; i<n; i++)
            for (int j=0; j<n; j++) {
                Interval iv = result.getElement(i, j);
                result.setElement(i, j, new Interval(iv.getLower()-rho, iv.getUpper()+rho));
            }
        
        return result;
    }
    
    public Matrix getUpper()
    {
        double m[][] = new double[this.getRow()][];
        for (int i=0; i<this.getRow(); i++){
            m[i] = new double[this.getColumn()];
            for (int j=0; j<this.getColumn(); j++)
                m[i][j] = this.getElement(i, j).getUpper();
        }
        return new Matrix(m);
    }
    
    public Matrix getLower()
    {
        double m[][] = new double[this.getRow()][];
        for (int i=0; i<this.getRow(); i++){
            m[i] = new double[this.getColumn()];
            for (int j=0; j<this.getColumn(); j++)
                m[i][j] = this.getElement(i, j).getLower();
        }
        return new Matrix(m);
    }
    
    public IntervalMatrix addCoefficient(IntervalMatrix m)
    {
        IntervalMatrix ret = new IntervalMatrix(this.getRow(), this.getColumn()+1);
        for (int i=0; i<this.getRow(); i++){
            for (int j=0; j<this.getColumn(); j++)
                ret.setElement(i, j, this.getElement(i, j));
            ret.setElement(i, this.getColumn(), m.getElement(i, 0));
        }
        ret.row    = this.row;
        ret.column = this.column+1;
        return ret;
    }
    
    public IntervalMatrix getCoefficient()
    {
        IntervalMatrix ret = new IntervalMatrix(this.getRow(), 1);
        for (int i=0; i<this.getRow(); i++)
            ret.setElement(i, 0, this.getElement(i, this.getColumn()-1));
        return ret;
    }
    
    public IntervalMatrix delCoefficient()
    {
        IntervalMatrix ret = new IntervalMatrix();
        ret.matrix     = this.matrix;
        ret.row    = this.row;
        ret.column = this.column-1;
        return ret;
    }
}
